//******************************************************** // Horizontal Sundial // NASS Sundial Design Tutorials // Part 4 Complete Sundial // Robert L. Kellogg, Ph.D. // 10 Apr 2020 // // This design is part of the creative commons //******************************************************** /* Tutorial #1 We're going to create the base of a sundial. Three variation are given: a circular sundial and a dial base with 6 or 8 sides (hexagon or octagon). We'll make the sundial base with a slight taper of the sides. This is just a bit of simple esthetics. A goal of making this sundial is to create module procedures such that we can see each step and if we want, experiment with changes to the dial features Tutorial #2 Then we add hour lines. The base of the hour lines is moved off-center to the south by a distance Loffset. We've left enough room around the hour lines that we can add hour numbers in the next tutorial Tutorial #3 We extrude one of three types of gnomons (simple, rounded, and setback) and then place the gnomon onto the dial. We make sure that the foot of the gnomon is placed on the 6am - 6pm line Tutorial #4 To accurately place the hour numbers with a displaced gnomon we must resort to solving the quadratic equation for placement and then make sure that we correctly rotate the number to be in alignment with the hour line. Sorry about the math. */ echo("Part-4 Adding Hour Numbers"); //NASS Tutorial // ---------------------------------------------------- // --------------- Sundial Parameters ---------------- // ---------------------------------------------------- // Note: base units are in mm and angles in degrees shape = "octagon"; // "circular" dial base // "hexagon" dial base // "octagon" dial base type = "setback"; //"simple" gnomon is simple triangle //"rounded" apex tip of gnomon rounded //"setback" base is set back logo = "NASS"; //logo for dial bottom dial_btm = 78; //dial base diam (~3") mm dial_top = 75; //dial base (taper top) mm dial_hght = 3; //dial base height mm lat = 40; //dial latitude deg minHA = 5; //first hour line am hour maxHA = 7; //last hour line pm hour Lwidth = 1; //hour line width mm Lhght = 1; //hour line height mm Llength = dial_top; //hour line length mm Loffset = .25*dial_top; //hour line & gnomon offset Dnrs = .78*dial_top; //ring for hour numbers Dout = .68*dial_top; //outer distance of chapter ring Din = .58*dial_top; //inner distance of chapter ring NrSize = 4.5; //Hour number size gbase = 35; //gnomon base for dial of 75mm gwidth = 2; //gnomon width mm gradi = 3; //rounding circle radius mm gsetback = 12; //gnomon base setback mm eps = .01; //epsilon merge mm // ---------------------------------------------------- // ------------ Main Program Starts Here ------------- // ---------------------------------------------------- if(shape=="circular") { echo("shape",shape); minangle = 3; sundial_base(minangle,turn=0); } if(shape=="octagon") { echo("shape",shape); minangle = 45; sundial_base(minangle,turn=45/2); } if(shape=="hexagon") { echo("shape",shape); minangle = 60; sundial_base(minangle,turn=0); } //add hour lines with numbers to the dial hour_lines(minHA,maxHA,lat); //add gnomon to the sundial if(type == "simple") { echo("type ",type); gnomon_1(lat,gbase,gwidth); } if(type == "rounded"){ echo("type ",type); gnomon_2(lat,gbase,gwidth,gradi); } if(type == "setback"){ echo("type ",type); gnomon_3(lat,gbase,gwidth,gradi,gsetback); } // ---------------------------------------------------- // ----------- Procedure Modules Start Here ----------- // ---------------------------------------------------- module hour_lines(minHA,maxHA,lat){ // first and last hour angle in degrees first = 15*(minHA - 12); last = 15*(maxHA); // loop through the hour angles for(HA=[first:15:last]){ theta = atan2(sin(lat)*sin(HA),cos(HA)); translate([0,0,dial_hght]) intersection(){ //mask the raw hour angle DonutMask(Din,Dout,Lhght); translate([0,-Loffset,0]) // raw hour angle rotate([0,0,theta]) translate([0,Llength/2,Lhght/2]) cube([Lwidth,Llength,Lhght],center=true); } //---- NEW FOR TUTORIAL PART 4 //insert the hour number for the hour line HourNumber(Dnrs,Lhght,HA,lat); } } module DonutMask(Din, Dout, Dhght){ $fn = 180; difference(){ cylinder(d=Dout,h=Dhght); cylinder(d=Din,h=3*Dhght,center=true); } } module HourNumber(Dnrs,Rhght,HA,lat){ // Dnrs diameter of HA lines mm // Rhght height of number mm // NrSize text size // HA hour angle (degrees) // lat dial latitude (degrees) Ro = Dnrs/2; //classic hour line angle theta = atan2(sin(lat)*sin(HA),cos(HA)); // Pythagorian for non-right triangle bo = -2*Loffset*cos(theta); co = Loffset*Loffset - Ro*Ro; // Solve quadratic equation zo = abs((-bo +sqrt(bo*bo-4*co))/2); //create hour line xo = zo*sin(theta); yo = zo*cos(theta)-Loffset+1; if(HA<=0){ txt = str(12+HA/15); translate([xo,yo,dial_hght-eps]) //rotate([0,0,-theta]) linear_extrude(height = Rhght) text(txt, size = NrSize, halign="center",valign="center",font = str("Angsana:style=Bold"), $fn = 24); }else{ txt = str(HA/15); translate([xo,yo,dial_hght-eps]) //rotate([0,0,-theta]) linear_extrude(height = Rhght) text(txt, size = NrSize, halign = "center",valign = "center",font = str("Angsana:style=Bold"), $fn = 24); } } module sundial_base(mini,turn){ $fa = mini; difference(){ //here we include a logo rotate([0,0,turn]) //notice no ';' since rotate cylinder cylinder($fa,h=dial_hght,d1=dial_btm,d2=dial_top); //engrave on bottom rotate([0,180,0]) translate([0,Din/3,-Lhght]) linear_extrude(height = 2*Lhght) text(logo, size = NrSize, halign = "center",valign = "center",font = str("Angsana:style=Bold"), $fn = 24); } //we use the built-in procedure called cylinder, using //the options to define a top and bottom size. //$fa sets the number of degrees for each cylinder segment // circular $fa=3, hexagon $fa=45, octagon $fa=60 //'center=false' still puts the cylinder center at xy = (0,0) //but the bottom of the cylinder rests at z=0 (that is, it //sets on the ground of the xy plane. //the size of the sundial base could have been passed as //variables, such as calling // sundial_base(dial_hght,dial_btm,dial_top,mini,turn); //and the procedure could be // sundial_base(hght,btm,top,mini,turn); //Notice that the rotate operator function occurs before the //object it acts upon. Hence rotate() appears in the line //above the cylinder object and has no ";". Only the object // cylinder(); gets the ";" } module gnomon_1(lat,b,w){ //simple triangular gnomon h = b*tan(lat); //height of gnomon gpoly = [[0,0],[b,0],[0,h]]; //simple triangle // rotate and move gnomon onto the dial translate([w/2,b-Loffset,dial_hght-eps]) rotate([0,0,-90]) rotate([90,0,0]) linear_extrude(height = w, convexity=3) //extrude polygon polygon(gpoly); //make polygon from points } module gnomon_2(lat,b,w,ro){ //rounded tip gnomon h = b*tan(lat); //height of gnomon z = b / cos(lat); //hypotenuse xi = (90 - lat)/2; //half apex angle hp = ro / tan(xi); //tangent distance ho = h - hp; //lower vertical distance zo = z - hp; //lower hypotenuse distance zx = b - zo*cos(lat); //x-tangent pt. on hypotenuse zy = zo*sin(lat); //y-tangent pt. on hypotenuse cx = ro; //x-circle center cy = ho; //y-circle center gpoly = [[0,0],[b,0],[zx,zy],[0,ho]]; //4-point polygon // rotate and move gnomon onto the dial translate([w/2,b-Loffset,dial_hght-eps]) rotate([0,0,-90]) rotate([90,0,0]) union(){ linear_extrude(height=w,convexity=3)//extrude polygon polygon(gpoly); //make polygon from points // add cylinder translate([cx,cy,0]) //move to rounding point cylinder(r = ro,h = w, $fn=180); //180 segments in cylinder } } module gnomon_3(lat,b,w,ro,back){ //setback & rounded tip gnomon h = b*tan(lat); //height of gnomon z = b / cos(lat); //hypoteneus xi = (90 - lat)/2; //half apex angle hp = ro / tan(xi); //tangent distance ho = h - hp; //lower vertical distance zo = z - hp; //lower hypotenuse distance zx = b - zo*cos(lat); //x-tangent pt. on hypotenuse zy = zo*sin(lat); //y-tangent pt. on hypotenuse cx = ro; //x-circle center cy = ho; //y-circle center // now find last tangent point R =sqrt((ro-back)*(ro-back) + (ho*ho)); //circle center to setback beta = acos(ro/R); //apex angle rLR triangle L = R*sin(beta); //setback side length num = (ro-back)/L + ho/ro; den = ro/L + L/ro; //num = ro*(ro-back) + L*ho; //alternate //den = ro*ro + L*L; alpha = acos(num/den); //tangent rotation angle tx = ro*(1-cos(alpha)); //x-tangent pt. of setback ty = ho-ro*sin(alpha); //y-tangent pt. of setback // 4-point poly with setback gpoly =[[back,0],[b,0],[zx,zy],[tx,ty]]; // rotate and move gnomon onto the dial translate([w/2,b-Loffset,dial_hght-eps]) rotate([0,0,-90]) rotate([90,0,0]) union(){ linear_extrude(height=w,convexity=3)//extrude polygon polygon(gpoly); //make polygon from points // add cylinder translate([cx,cy,0]) //move to rounding point cylinder(r = ro,h = w, $fn=180); //180 segments in cylinder } }